package com.zzh.lib.protector;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 程序保护
 */
public class HProtector {

    public static boolean isDebug = false;

    public static String checkSignature(Context context) {
        return SecurityCheckUtil.getSingleInstance().getSignature(context);
    }

    /**
     * 是否是Debug模式
     *
     * @param context
     * @return
     */
    public static boolean checkIsDebug(Context context) {
        return SecurityCheckUtil.getSingleInstance().checkIsDebugVersion(context) ||
                SecurityCheckUtil.getSingleInstance().checkIsDebuggerConnected();
    }

    /**
     * 设备是否被ROOT
     *
     * @return true被
     */
    public static boolean checkIsRoot() {
        return SecurityCheckUtil.getSingleInstance().isRoot();
    }

    /**
     * 检测Xposed是否存在
     *
     * @return true 存在
     */
    public static boolean checkIsXposedExist() {
        return SecurityCheckUtil.getSingleInstance().isXposedExistByThrow();
    }

    /**
     * 检测Xposed是否存在，存在则禁用它
     *
     * @return true 禁用成功
     */
    public static boolean checkXposedExistAndDisableIt() {
        return SecurityCheckUtil.getSingleInstance().tryShutdownXposed();
    }

    /**
     * Java层级检测是否被调试。
     *
     * @return true 调试。
     */
    public static boolean checkIsBeingTracedByJava() {
        return SecurityCheckUtil.getSingleInstance().readProcStatus();
    }

    /**
     * C 层级检测是否被调试。
     */
    public static int checkIsBeingTracedByC() {
        return ZHAppSafe.startSafePrevent(true, null);
    }

    /**
     * 检测进程
     *
     * @return 0 执行成功
     */
    public static int checkIsMemProcess() {
        return ZHAppSafe.startSafePreventServer(true, null);
    }


    /**
     * 检测私有文件路径是否有LLDB文件夹
     *
     * @param ctx
     * @return 0 执行成功
     */
    public static int checkIsPackageProcess(Context ctx) {
        return ZHAppSafe.startSafePreventPathServer(true, ctx.getPackageName(), null);
    }

    /**
     * 是否运行在模拟器里面。
     *
     * @param context  上下文变量
     * @param callback 回调
     * @return
     */
    public static boolean checkIsRunningInEmulator(Context context, EmulatorCheckCallback callback) {
        return EmulatorCheckUtil.getSingleInstance().readSysProperty(context, callback);
    }

    /**
     * 是否运行在虚拟机里面
     *
     * @param uniqueMsg
     * @param callback
     * @return
     */
    public static boolean checkIsRunningInVirtualApk(String uniqueMsg, VirtualCheckCallback callback) {
        return VirtualApkCheckUtil.getSingleInstance().checkByCreateLocalServerSocket(uniqueMsg, callback);
    }

    /**
     * 子线程轮训，检测TracerId
     *
     * @return
     */
    public static boolean startSafeGuard(Context ctx) {
        try {
            checkIsBeingTracedByC();
            checkIsMemProcess();
            checkIsPackageProcess(ctx);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * C层退出程序。
     *
     * @return
     */
    public static int safeCheckKillServer() {
        int safeKillSelfServer = ZHAppSafe.safeKillSelfServer();
        if (isDebug) {
            Log.d("--------HProtector---", "----code: " + safeKillSelfServer);
        }
        return safeKillSelfServer;
    }

    /**
     * 检测App是否hook
     *
     * @param context
     * @return
     */
    public static boolean isHook(Context context) {
        if (findHookAppName(context) || findHookAppFile() || findHookStack()) {
            return true;
        }
        return false;
    }

    /**
     * 查找程序运行的栈中是否存在hook相关类
     *
     * @return
     */
    private static boolean findHookStack() {
        try {
            throw new Exception("findhook");
        } catch (Exception e) {
            int zygoteInitCallCount = 0;
            for (StackTraceElement stackTraceElement : e.getStackTrace()) {
                if (stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) {
                    zygoteInitCallCount++;
                    if (zygoteInitCallCount == 2) {
                        Log.wtf("HookDetection", "Substrate is active on the device.");
                        return true;
                    }
                }
                if (stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2")
                        && stackTraceElement.getMethodName().equals("invoked")) {
                    Log.wtf("HookDetection", "A method on the stack trace has been hooked using Substrate.");
                    return true;
                }
                if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge")
                        && stackTraceElement.getMethodName().equals("main")) {
                    Log.wtf("HookDetection", "Xposed is active on the device.");
                    return true;
                }
                if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge")
                        && stackTraceElement.getMethodName().equals("handleHookedMethod")) {
                    Log.wtf("HookDetection", "A method on the stack trace has been hooked using Xposed.");
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 查找设备存储中是否存在hook安装文件
     *
     * @return
     */
    private static boolean findHookAppFile() {
        try {
            Set<String> libraries = new HashSet<String>();
            String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps";
            BufferedReader reader = new BufferedReader(new FileReader(mapsFilename));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.endsWith(".so") || line.endsWith(".jar")) {
                    int n = line.lastIndexOf(" ");
                    libraries.add(line.substring(n + 1));
                }
            }
            reader.close();
            for (String library : libraries) {
                if (library.contains("com.saurik.substrate")) {
                    Log.wtf("HookDetection", "Substrate shared object found: " + library);
                    return true;
                }
                if (library.contains("XposedBridge.jar")) {
                    Log.wtf("HookDetection", "Xposed JAR found: " + library);
                    return true;
                }
            }
        } catch (Exception e) {
            Log.wtf("HookDetection", e.toString());
        }
        return false;
    }

    /**
     * 查找设备安装目录中是否存在hook工具
     *
     * @param context
     * @return true 已经是hook
     */
    private static boolean findHookAppName(Context context) {
        PackageManager packageManager = context.getPackageManager();
        List<ApplicationInfo> applicationInfoList = packageManager
                .getInstalledApplications(PackageManager.GET_META_DATA);
        for (ApplicationInfo applicationInfo : applicationInfoList) {
            //Xpose 框架
            if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
                Log.wtf("HookDetection", "Xposed found on the system.");
                return true;
            }
            //Cydia 框架
            if (applicationInfo.packageName.equals("com.saurik.substrate")) {
                Log.wtf("HookDetection", "Substrate found on the system.");
                return true;
            }
            //Magisk Manager 框架
            if (TextUtils.equals("com.topjohnwu.magisk", applicationInfo.packageName)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 判断设备 是否使用代理上网
     */
    public static final boolean isWifiProxy(Context context) {
        String proxyAddress;
        int proxyPort;
        proxyAddress = System.getProperty("http.proxyHost");
        String portStr = System.getProperty("http.proxyPort");
        proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
        return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
    }
}
